﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: NomialDistributionBase.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Linq;
using BOL.Linq.Probability;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Exposes a probability to each outcome of a finite sample space of a type T.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    public abstract class NomialDistributionBase<TKey> : INomialDistribution<TKey>, IEquatable<NomialDistributionBase<TKey>>
    {
        #region Private variables

        private IDictionary<TKey, double> _probabilityTable;

        #endregion

        #region Public properties

        public int K { get { return _probabilityTable.Count; } }

        /// <summary>Gets or sets the frequency table of a Binomial distribution.</summary>
        public virtual IDictionary<TKey, double> ProbabilityTable
        {
            get { return _probabilityTable; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _probabilityTable = value.Normalize();
            }
        }

        #endregion

        #region Constructor

        protected NomialDistributionBase(IDictionary<TKey, double> probabilityTable)
        {
            if (probabilityTable == null)
                throw new ArgumentNullException("probabilityTable");

            _probabilityTable = probabilityTable.Normalize();
        }

        protected NomialDistributionBase(ICollection<TKey> keys)
            : this(keys.Zip(Enumerable.Repeat(1.0, keys.Count), (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v)) { }

        #endregion

        #region Public methods

        /// <summary>
        /// Estimates parameters of the Bernoulli distribution using maximum likelihood.
        /// </summary>
        public void MaximumLikelihoodEstimate(IEnumerable<TKey> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var list = source.ToList();
            var n = list.Count;
            var proportion = list.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count() / (double)n);
            ProbabilityTable = ProbabilityTable.ToDictionary(x => x.Key, x => list.Count(y => y.Equals(x.Key)) / (double)n);
        }

        /// <summary>
        /// Estimates parameters of the Bernoulli distribution using Bayesian methods.
        /// <param name="priorParameters">Dirichlet priors</param>
        /// </summary>
        public void BayesianEstimate(IEnumerable<TKey> source, params dynamic[] priorParameters)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (K != priorParameters.Length)
                throw new ArgumentOutOfRangeException("priorParameters");

            var list = source.ToList();
            var n = list.Count;
            var alpha = priorParameters.Cast<double>().ToList();
            var sa = alpha.Sum();
            ProbabilityTable = ProbabilityTable.Zip(alpha, (pt, a) => new { Outcome = pt.Key, Probability = (list.Count(y => y.Equals(pt.Key)) + a) / (n + sa) }).ToDictionary(x => x.Outcome, x => x.Probability);
            ProbabilityTable = list.GroupBy(x => x).Zip(alpha, (l, a) => new { Outcome = l.Key, Probability = (l.Count() + a) / (n + sa) }).ToDictionary(x => x.Outcome, x => x.Probability);
        }

        /// <summary>
        /// Estimates parameters of the chi square distribution using Bayesian methods by uniform priors.
        /// </summary>
        public void LaplaceEstimate(IEnumerable<TKey> source)
        {
            BayesianEstimate(source, Enumerable.Repeat(1.0, K).ToArray());
        }

        #endregion

        #region IEquatable<TKey> implementation

        public bool Equals(NomialDistributionBase<TKey> other)
        {
            return ProbabilityTable.DictionaryEqual(other.ProbabilityTable);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return ProbabilityTable.Aggregate(1, (hashCode, p) => hashCode ^ p.Value.GetHashCode());
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is NomialDistributionBase<TKey>))
                throw new InvalidCastException("The 'other' argument is not a NomialDistributionBase<TKey> object.");

            return Equals(other as NomialDistributionBase<TKey>);
        }

        #endregion
    }
}
